// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <vector>
#include <turbo/log/logging.h>
#include <turbo/utility/status.h>
#include <turbo/container/span.h>
#include <cantor/type/value.h>

namespace cantor {
    typedef turbo::Status (*func_factor)(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out);

    class FunctionCaller {
    public:
        FunctionCaller() = default;

        FunctionCaller(func_factor f) : functor_(f) {
        }

        virtual ~FunctionCaller() = default;

        virtual turbo::Status call_func(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
            KCHECK(functor_) << "functor is not set";
            return functor_(args, out);
        }

    private:
        func_factor functor_{nullptr};
    };

    template<typename R, typename A1, typename F>
    turbo::Status scalar_unary_executor(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
        if (args.size() != 1) {
            return turbo::invalid_argument_error("invalid number of arguments should be 1 but got ", args.size());
        }
        auto type = CppTypeConvertTag<R>::kind;
        TURBO_MOVE_OR_RAISE(auto arg1, ValueView::create(args[0],false));
        ValueBuilder builder(out);
        if (arg1.is_null()) {
            builder.set_null(type);
            return turbo::OkStatus();
        }
        TURBO_MOVE_OR_RAISE(auto a1, arg1.as_type<A1>());
        R ret = F()(a1);
        auto rs = builder.set_up(ret);
        TURBO_RETURN_NOT_OK(rs);
        return turbo::OkStatus();
    }

    template<typename R, typename A1, typename A2, typename F>
    turbo::Status scalar_binary_executor(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
        if (args.size() != 2) {
            return turbo::invalid_argument_error("invalid number of arguments should be 2 but got ", args.size());
        }
        auto type = CppTypeConvertTag<R>::kind;
        TURBO_MOVE_OR_RAISE(auto arg1, ValueView::create(args[0],false));
        TURBO_MOVE_OR_RAISE(auto arg2, ValueView::create(args[1],false));
        ValueBuilder builder(out);
        if (arg1.is_null() || arg2.is_null()) {
            builder.set_null(type);
            return turbo::OkStatus();
        }
        TURBO_MOVE_OR_RAISE(auto a1, arg1.as_type<A1>());
        TURBO_MOVE_OR_RAISE(auto a2, arg2.as_type<A2>());
        R ret = F()(a1, a2);
        auto rs = builder.set_up(ret);
        TURBO_RETURN_NOT_OK(rs);
        return turbo::OkStatus();
    }

    /// TernaryExecutor
    template<typename R, typename A1, typename A2, typename A3, typename F>
    turbo::Status scalar_ternary_executor(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
        if (args.size() != 3) {
            return turbo::invalid_argument_error("invalid number of arguments should be 3 but got ", args.size());
        }
        auto type = CppTypeConvertTag<R>::kind;
        TURBO_MOVE_OR_RAISE(auto arg1, ValueView::create(args[0],false));
        TURBO_MOVE_OR_RAISE(auto arg2, ValueView::create(args[1],false));
        TURBO_MOVE_OR_RAISE(auto arg3, ValueView::create(args[2],false));
        ValueBuilder builder(out);
        if (arg1.is_null() || arg2.is_null() || arg3.is_null()) {
            builder.set_null(type);
            return turbo::OkStatus();
        }
        TURBO_MOVE_OR_RAISE(auto a1, arg1.as_type<A1>());
        TURBO_MOVE_OR_RAISE(auto a2, arg2.as_type<A2>());
        TURBO_MOVE_OR_RAISE(auto a3, arg3.as_type<A3>());
        R ret = F()(a1, a2, a3);
        auto rs = builder.set_up(ret);
        TURBO_RETURN_NOT_OK(rs);
        return turbo::OkStatus();
    }

    /// QuaternaryExecutor
    template<typename R, typename A1, typename A2, typename A3, typename A4, typename F>
    turbo::Status scalar_quaternary_executor(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
        if (args.size() != 4) {
            return turbo::invalid_argument_error("invalid number of arguments should be 4 but got ", args.size());
        }
        auto type = CppTypeConvertTag<R>::kind;
        TURBO_MOVE_OR_RAISE(auto arg1, ValueView::create(args[0],false));
        TURBO_MOVE_OR_RAISE(auto arg2, ValueView::create(args[1],false));
        TURBO_MOVE_OR_RAISE(auto arg3, ValueView::create(args[2],false));
        TURBO_MOVE_OR_RAISE(auto arg4, ValueView::create(args[3],false));
        ValueBuilder builder(out);
        if (arg1.is_null() || arg2.is_null() || arg3.is_null() || arg4.is_null()) {
            builder.set_null(type);
            return turbo::OkStatus();
        }
        TURBO_MOVE_OR_RAISE(auto a1, arg1.as_type<A1>());
        TURBO_MOVE_OR_RAISE(auto a2, arg2.as_type<A2>());
        TURBO_MOVE_OR_RAISE(auto a3, arg3.as_type<A3>());
        TURBO_MOVE_OR_RAISE(auto a4, arg4.as_type<A4>());
        R ret = F()(a1, a2, a3, a4);
        auto rs = builder.set_up(ret);
        TURBO_RETURN_NOT_OK(rs);
        return turbo::OkStatus();
    }

    /// QuinaryExecutor
    template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5,typename F>
  turbo::Status scalar_quinary_executor(turbo::span<const cantor::pb::Value> &args, cantor::pb::Value &out) {
        if (args.size() != 5) {
            return turbo::invalid_argument_error("invalid number of arguments should be 5 but got ", args.size());
        }
        auto type = CppTypeConvertTag<R>::kind;
        TURBO_MOVE_OR_RAISE(auto arg1, ValueView::create(args[0],false));
        TURBO_MOVE_OR_RAISE(auto arg2, ValueView::create(args[1],false));
        TURBO_MOVE_OR_RAISE(auto arg3, ValueView::create(args[2],false));
        TURBO_MOVE_OR_RAISE(auto arg4, ValueView::create(args[3],false));
        TURBO_MOVE_OR_RAISE(auto arg5, ValueView::create(args[4],false));
        ValueBuilder builder(out);
        if (arg1.is_null() || arg2.is_null() || arg3.is_null() || arg4.is_null() || arg5.is_null()) {
            builder.set_null(type);
            return turbo::OkStatus();
        }
        TURBO_MOVE_OR_RAISE(auto a1, arg1.as_type<A1>());
        TURBO_MOVE_OR_RAISE(auto a2, arg2.as_type<A2>());
        TURBO_MOVE_OR_RAISE(auto a3, arg3.as_type<A3>());
        TURBO_MOVE_OR_RAISE(auto a4, arg4.as_type<A4>());
        TURBO_MOVE_OR_RAISE(auto a5, arg5.as_type<A5>());
        R ret = F()(a1, a2, a3, a4, a5);
        auto rs = builder.set_up(ret);
        TURBO_RETURN_NOT_OK(rs);
        return turbo::OkStatus();
    }
} // namespace cantor
